<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
Test swapFrameLoaders with different frame types and remoteness
-->
<window title="Mozilla Bug 1242644"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>

  <script type="application/javascript"><![CDATA[
  ["SimpleTest", "SpecialPowers", "info", "is", "ok"].forEach(key => {
    window[key] = window.opener[key];
  })
  const { interfaces: Ci } = Components;

  const NS = {
    xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
    html: "http://www.w3.org/1999/xhtml",
  }

  const TAG = {
    xul: "browser",
    html: "iframe", // mozbrowser
  }

  const SCENARIOS = [
    ["xul", "xul"],
    ["xul", "html"],
    ["html", "xul"],
    ["html", "html"],
    ["xul", "xul", "remote"],
    ["xul", "html", "remote"],
    ["html", "xul", "remote"],
    ["html", "html", "remote"],
  ];

  const HEIGHTS = [
    200,
    400
  ];

  function frameScript() {
    addEventListener("load", function onLoad() {
      sendAsyncMessage("test:load");
    }, true);
  }

  // Watch for loads in new frames
  window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true);

  function once(target, eventName, useCapture = false) {
    info("Waiting for event: '" + eventName + "' on " + target + ".");

    return new Promise(resolve => {
      for (let [add, remove] of [
        ["addEventListener", "removeEventListener"],
        ["addMessageListener", "removeMessageListener"],
      ]) {
        if ((add in target) && (remove in target)) {
          target[add](eventName, function onEvent(...aArgs) {
            info("Got event: '" + eventName + "' on " + target + ".");
            target[remove](eventName, onEvent, useCapture);
            resolve(aArgs);
          }, useCapture);
          break;
        }
      }
    });
  }

  function* addFrame(type, remote, height) {
    let frame = document.createElementNS(NS[type], TAG[type]);
    frame.setAttribute("remote", remote);
    if (remote && type == "xul") {
      frame.setAttribute("style", "-moz-binding: none;");
    }
    if (type == "html") {
      frame.setAttribute("mozbrowser", "true");
      frame.setAttribute("noisolation", "true");
      frame.setAttribute("allowfullscreen", "true");
    } else if (type == "xul") {
      frame.setAttribute("type", "content");
    }
    let src = `data:text/html,<!doctype html>` +
              `<body style="height:${height}px"/>`;
    frame.setAttribute("src", src);
    document.documentElement.appendChild(frame);
    let mm = frame.frameLoader.messageManager;
    yield once(mm, "test:load");
    return frame;
  }

  add_task(function*() {
    yield new Promise(resolve => {
      SpecialPowers.pushPrefEnv(
        { "set": [["dom.mozBrowserFramesEnabled", true],
                  ["network.disable.ipc.security", true]] },
        resolve);
    });
  });

  add_task(function*() {
    for (let scenario of SCENARIOS) {
      let [ typeA, typeB, remote ] = scenario;
      remote = !!remote;
      let heightA = HEIGHTS[0];
      info(`Adding frame A, type ${typeA}, remote ${remote}, height ${heightA}`);
      let frameA = yield addFrame(typeA, remote, heightA);

      let heightB = HEIGHTS[1];
      info(`Adding frame B, type ${typeB}, remote ${remote}, height ${heightB}`);
      let frameB = yield addFrame(typeB, remote, heightB);

      let frameScriptFactory = function(name) {
        return `function() {
          addMessageListener("ping", function() {
            sendAsyncMessage("pong", "${name}");
          });
          addMessageListener("check-browser-api", function() {
            let exists = "api" in this;
            sendAsyncMessage("check-browser-api", {
              exists,
              running: exists && !this.api._shuttingDown,
            });
          });
        }`;
      }

      // Load frame script into each frame
      {
        let mmA = frameA.frameLoader.messageManager;
        let mmB = frameB.frameLoader.messageManager;

        mmA.loadFrameScript("data:,(" + frameScriptFactory("A") + ")()", false);
        mmB.loadFrameScript("data:,(" + frameScriptFactory("B") + ")()", false);
      }

      // Ping before swap
      {
        let mmA = frameA.frameLoader.messageManager;
        let mmB = frameB.frameLoader.messageManager;

        let inflightA = once(mmA, "pong");
        let inflightB = once(mmB, "pong");

        info("Ping message manager for frame A");
        mmA.sendAsyncMessage("ping");
        let [ { data: pongA } ] = yield inflightA;
        is(pongA, "A", "Frame A message manager gets reply A before swap");

        info("Ping message manager for frame B");
        mmB.sendAsyncMessage("ping");
        let [ { data: pongB } ] = yield inflightB;
        is(pongB, "B", "Frame B message manager gets reply B before swap");
      }

      // Check height before swap
      {
        if (frameA.getContentDimensions) {
          let { height } = yield frameA.getContentDimensions();
          is(height, heightA, "Frame A's content height is 200px before swap");
        }
        if (frameB.getContentDimensions) {
          let { height } = yield frameB.getContentDimensions();
          is(height, heightB, "Frame B's content height is 400px before swap");
        }
      }

      // Ping after swap using message managers acquired before
      {
        let mmA = frameA.frameLoader.messageManager;
        let mmB = frameB.frameLoader.messageManager;

        info("swapFrameLoaders");
        frameA.swapFrameLoaders(frameB);

        let inflightA = once(mmA, "pong");
        let inflightB = once(mmB, "pong");

        info("Ping message manager for frame A");
        mmA.sendAsyncMessage("ping");
        let [ { data: pongA } ] = yield inflightA;
        is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap");

        info("Ping message manager for frame B");
        mmB.sendAsyncMessage("ping");
        let [ { data: pongB } ] = yield inflightB;
        is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap");
      }

      // Check height after swap
      {
        if (frameA.getContentDimensions) {
          let { height } = yield frameA.getContentDimensions();
          is(height, heightB, "Frame A's content height is 400px after swap");
        }
        if (frameB.getContentDimensions) {
          let { height } = yield frameB.getContentDimensions();
          is(height, heightA, "Frame B's content height is 200px after swap");
        }
      }

      // Ping after swap using message managers acquired after
      {
        let mmA = frameA.frameLoader.messageManager;
        let mmB = frameB.frameLoader.messageManager;

        let inflightA = once(mmA, "pong");
        let inflightB = once(mmB, "pong");

        info("Ping message manager for frame A");
        mmA.sendAsyncMessage("ping");
        let [ { data: pongA } ] = yield inflightA;
        is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap");

        info("Ping message manager for frame B");
        mmB.sendAsyncMessage("ping");
        let [ { data: pongB } ] = yield inflightB;
        is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
      }

      // Verify browser API frame scripts destroyed if swapped out of browser frame
      if (frameA.hasAttribute("mozbrowser") != frameB.hasAttribute("mozbrowser")) {
        let mmA = frameA.frameLoader.messageManager;
        let mmB = frameB.frameLoader.messageManager;

        let inflightA = once(mmA, "check-browser-api");
        let inflightB = once(mmB, "check-browser-api");

        info("Check browser API for frame A");
        mmA.sendAsyncMessage("check-browser-api");
        let [ { data: apiA } ] = yield inflightA;
        if (frameA.hasAttribute("mozbrowser")) {
          ok(apiA.exists && apiA.running, "Frame A browser API exists and is running");
        } else {
          ok(apiA.exists && !apiA.running, "Frame A browser API did exist but is now destroyed");
        }

        info("Check browser API for frame B");
        mmB.sendAsyncMessage("check-browser-api");
        let [ { data: apiB } ] = yield inflightB;
        if (frameB.hasAttribute("mozbrowser")) {
          ok(apiB.exists && apiB.running, "Frame B browser API exists and is running");
        } else {
          ok(apiB.exists && !apiB.running, "Frame B browser API did exist but is now destroyed");
        }
      } else {
        info("Frames have matching mozbrowser state, skipping browser API destruction check");
      }

      frameA.remove();
      frameB.remove();
    }
  });
  ]]></script>
</window>
